#pragma once

#include <QObject>
#include <QtScript>

namespace framework { namespace script {

template< typename T >
struct ValueType {
  typedef T TValue;
};

template < typename RT >
struct ValueType< RT &> {
  typedef RT TValue;
};

template < typename RT >
struct ValueType< RT const &> {
  typedef RT TValue;
};

inline QScriptValue to_script_cast(QScriptValue const &val, QScriptEngine * pEngine)
{
  return val;
}

template< typename T>
QScriptValue to_script_cast(T const &val, QScriptEngine * pEngine)
{
  QVariant var = qVariantFromValue(val);
  return pEngine->newVariant(var);
}

template< typename T >
QScriptValue to_script_cast(T * const pVal, QScriptEngine * pEngine)
{
  QObject * pObject = qobject_cast<QObject *>(pVal);

  if (pObject)
    return pEngine->newQObject(pObject);

  return QScriptValue();
}

template< typename T>
void from_script_cast(const QScriptValue &val, QScriptEngine * pEngine, T & ret)
{
  ret = qscriptvalue_cast<T>(val);
}

template < typename T >
void from_script_cast(const QScriptValue &val, QScriptEngine * pEngine, T *& ret)
{
  if (val.isQObject())
    ret = val.toQObject();
  else
    ret = 0;
}

template< typename RT >
struct CallWrappers
{
  static QScriptValue Call(RT (*func)(), QScriptContext *pContext, QScriptEngine * pEngine)
  {
    return to_script_cast(func(), pEngine);
  }

  template< typename P1 >
  static QScriptValue Call(RT (*func)(P1), QScriptContext *pContext, QScriptEngine * pEngine)
  {
    ValueType<P1>::TValue p1;

    from_script_cast(pContext->argument(0), pEngine, p1);

    return to_script_cast(func(p1), pEngine);
  }

  template < typename P1, typename P2 >
  static QScriptValue Call(RT (*func)(P1, P2), QScriptContext * pContext, QScriptEngine * pEngine)
  {
    ValueType<P1>::TValue p1;
    ValueType<P2>::TValue p2;

    from_script_cast(pContext->argument(0), pEngine, p1);
    from_script_cast(pContext->argument(1), pEngine, p2);

    return to_script_cast(func(p1, p2), pEngine);
  }
};

template<>
struct CallWrappers<void>
{
  static QScriptValue Call(void (*func)(), QScriptContext *pContext, QScriptEngine * pEngine)
  {
    func();
    return QScriptValue();
  }

  template < typename P1>
  static QScriptValue Call(void (*func)(P1), QScriptContext *pContext, QScriptEngine * pEngine)
  {
    ValueType<P1>::TValue p1;
    from_script_cast(pContext->argument(0), pEngine, p1);

    func(p1);

    return QScriptValue();
  }

  template < typename P1, typename P2 >
  static QScriptValue Call(void (*func)(P1, P2), QScriptContext * pContext, QScriptEngine * pEngine)
  {
    ValueType<P1>::TValue p1;
    ValueType<P2>::TValue p2;

    from_script_cast(pContext->argument(0), pEngine, p1);
    from_script_cast(pContext->argument(1), pEngine, p2);

    func(p1, p2);

    return QScriptValue();
  }
};

template<typename RT>
QScriptValue Call(RT (*func)(), QScriptContext *context, QScriptEngine * pEngine)
{
  return CallWrappers<RT>::Call(func, context, pEngine);
}

template< typename RT, typename P1 >
QScriptValue Call(RT (*func)(P1), QScriptContext *context, QScriptEngine * pEngine)
{
  return CallWrappers<RT>::Call(func, context, pEngine);
}

template< typename RT, typename P1, typename P2 >
QScriptValue Call(RT (*func)(P1, P2), QScriptContext *context, QScriptEngine * pEngine)
{
  return CallWrappers<RT>::Call(func, context, pEngine);
}

template<class FUNC>
QScriptValue WrapFunction(QScriptContext *context, QScriptEngine *pEngine)
{
  QScriptValue callee = context->callee();

  int data = qscriptvalue_cast< int >(callee.data());

  FUNC func = FUNC(data);

  return Call(*func, context, pEngine);
}

template< class FUNC >
void RegisterFunction(QScriptEngine &Engine_, const char * pFunctionName_, FUNC F_)
{
  QScriptValue functionValue = Engine_.newFunction(WrapFunction< FUNC >);
  functionValue.setData(QScriptValue((int) (F_)));
  Engine_.globalObject().setProperty(pFunctionName_, functionValue);
}

} } // namespace script { namespace script {